module etc.dleveldb.slice;

import deimos.leveldb.c;

import std.traits;
import std.exception : enforce;

import std.logger;

unittest
{
    string s1 = "abc";

    auto v1 = Slice("abc");
    auto v2 = Slice("abc");
    auto v3 = Slice(s1.ptr, s1.length);
    auto v4 = Slice("bcd");

    assert(v1.length == 3);
    assert(v2.length == 3);
    assert(v1 == v2);

    assert(v1 != v4);

}

unittest
{
    trace("start.");
    assert(_lib_obj_size__("1234567890") == 10);
    assert(_lib_obj_size__("1234") == 4);
    int i = 123_567;
    assert(_lib_obj_size__(i) == int.sizeof);
    long l = 123_567;
    assert(_lib_obj_size__(l) == long.sizeof);
    double d = 123_567;
    assert(_lib_obj_size__(d) == double.sizeof);
}

unittest
{
    trace("start.");

    struct ty
    {
        string v1;
    }

    auto v2 = ty("abc");

    //assert(v2);

    auto s = "Hello";
    auto s1 = Slice(s);
    auto s2 = Slice(true);

    auto r = cast(bool) s2;

    assert(r);

    /// Bugs:  无法以bool方式比较非bool的数据
    //assert(s1, "Slice is not valid");
    assert(s1.isValid);
    assert(s1.length == 5);
    assert(s1.ptr!(const(char*)) == s.ptr);
    assert(s1.length == s.length);
    assert(s1.as!string == s);
}

unittest
{
    trace("start.");
    auto s = "Hello World";
    auto s1 = Slice(s[0 .. 5]);
    assert(s1.isValid);
    assert(s1.length == 5);
    assert(s1.ptr!(const(char*)) == s.ptr);
    assert(s1.length == s[0 .. 5].length);
    assert(s1.as!string == s[0 .. 5]);
}

unittest
{
    trace("start.");
    int s = 454;
    auto s1 = Slice(s);
    assert(s1.isValid);
    assert(s1.length == int.sizeof);
    assert(s1.length == s.sizeof);
    assert(s1.ptr!(int*) == &s);
    assert(s1.as!int == s);
}

unittest
{
    trace("start.");
    struct Point(T)
    {
        T x, y;
    }

    auto p1 = Point!int(1, 2);
    auto s = Slice(p1);
    assert(s.isValid);
    assert(s.length == int.sizeof * 2);
    assert(s.as!(Point!int).x == 1);
    assert(s.as!(Point!int).y == 2);
    /*
    try
    {
        s.as!(Point!long);
        assert(false, "Should have thrown");
    }
    catch (LeveldbException e)
    {
    }
    catch (Exception e)
    {
        assert(false, "Should have thrown a LeveldbException");
    }
    */

    s = Slice(new Point!real(10, 12));
    assert(s.length == real.sizeof * 2);
}

unittest
{
    trace("start.");
    align(1) struct Ref(T)
    {
        T t;
    }

    auto s1 = Slice(new Ref!int(451), 4);
    assert(s1.isValid, "s1.ok");
    assert(s1.length == int.sizeof, "s1.length == int.sizeof");
    assert(s1.as!int == 451, "s1.as!int == 451");

    /// Make a safe constant slice
    s1 = Slice.Ref(999);
    assert(s1.isValid, "s1.ok");
    assert(s1.length == int.sizeof, "s1.length == int.sizeof");
    assert(s1.as!int == 999, "s1.as!int == 999");
}

/**
 * Holds a pointer returned from leveldb, or passed to leveldb.
 *
 * Leveldb memory is freed on destruction.
 */
struct Slice
{
    private
    {
        union
        {
            struct
            {
                size_t _len;
                void* _ptr;
            }

            ubyte[] _raw;
            void[] _void;
        }

        bool _free = false;

    }

package:
    /// Used by DB class to hold leveldb raw pointers
    this(P = void*)(void* p, size_t l, bool free)
    {
        this._free = free;
        _ptr = p;
        _len = l;
    }

public:
    /// Takes reference
    this(P)(ref P p)
    {
        this(p._lib_obj_ptr__, p._lib_obj_size__);
    }

    this(P)(in P p)
            if (!__traits(isRef, p))
    {
        this(p._lib_obj_ptr__, p._lib_obj_size__);
    }

    /// Takes reference
    this(P)(P p, size_t l)
            if (isPointer!P)
    {
        _ptr = cast(void*) p;
        _len = l;
    }

    /// Calles free on leveldb raw memory
    ~this()
    {
        if (_free && (_ptr !is null))
        {
            leveldb_free(_ptr);
            _free = false;
            _ptr = null;
            _len = 0;
        }

    }

    /// Get slice pointer
    @property
    inout(T) ptr(T)() inout
            if (isPointer!T)
    {
        return cast(inout(T)) _ptr;
    }

    /** 
     * 切片保存为一个数据类型
     *Params: 
     * T = 数据类型
     * Returns: 
     *Bugs: 
     * 返回为副本 dup
     */
    @property
    inout(T) as(T)() inout
            if (!isPointer!T && __traits(compiles, *(cast(inout(T*)) _ptr)))
    {

        static if (isSomeString!T || isArray!T)
        {
            return cast(T) this._raw.dup;
        }
        else static if (is(T == class))
        {
            enforce(!(__traits(classInstanceSize, T) > length), "Casting size is larger then slice data");
            return *(cast(inout(T*))(this._raw.dup.ptr));
        }
        else
        {
            enforce(T.sizeof <= length, "Casting size is larger then slice data");
            if (_free)
            {
                return *(cast(inout(T*))(this._raw.dup.ptr));
            }
            else
            {
                return *(cast(inout(T*))(this._raw.ptr));
            }

        }
    }

    /// length or size of slice
    @property
    size_t length() inout
    {
        return _len;
    }

    /// 测试切片指针是否有效
    @property
    bool isValid() inout
    {
        return _ptr !is null;
    }
    /// 判定自身是否为空
    @property
    bool isEmpty() inout
    {
        return _ptr is null;
    }

    /// Slice casting
    inout(T) opCast(T)() inout
    {
        static if (isPointer!T)
        {
            return ptr!T;
        }
        else
        {
            return as!T;
        }

    }

    // 同包使用
    package
    {
        alias to = as;
        alias _lib_obj_ptr__ = ptr!(const(char*));
        alias _lib_obj_size__ = length;
    }

    /// Create a safe refrence for slicing, good for primitive type constants
    static Slice Ref(T)(T t)
    {
        align(1) static struct Ref
        {
            T t;
        }

        return Slice(new Ref(t), T.sizeof);
    }
}

package:

/// Find the byte size of a valid Slice type
size_t _lib_obj_size__(P)(in P p)
        if (isSomeString!P || ((isStaticArray!P || isDynamicArray!P) && !isBanned!(ForeachType!P)))
{
    return p.length ? p[0].sizeof * p.length : 0;
}

/// Find the byte size of a valid Slice type
size_t _lib_obj_size__(P)(in P p)
        if (isBasicType!P || isPODStruct!P)
{
    return P.sizeof;
}

/// Find the byte size of a valid Slice type
size_t _lib_obj_size__(P)(in P p)
        if (isPointer!P)
{
    return _lib_obj_size__(*p);
}

/// Find the pointer of a valid Slice type
const(char)* _lib_obj_ptr__(P)(ref P p)
{
    static if ((isArray!P && !isBanned!(ForeachType!P)))
        return cast(const(char*)) p.ptr;
    else static if (isBasicType!P || isPODStruct!P)
        return cast(const(char*))(&p);
    else static if (isPointer!P)
        return _lib_obj_ptr__(*p);
    else
        assert(false, "Not a valid type for leveldb slice: ref " ~ typeof(p).stringof);
}

template isBanned(T)
{
    static if (is(T == class) || isDynamicArray!T || isPointer!T)
        enum isBanned = true;
    else
        enum isBanned = false;
}

template isPODStruct(T)
{
    static if (is(T == struct))
        enum isPODStruct = __traits(isPOD, T);
    else
        enum isPODStruct = false;
}
